<?php
class CGenerate
{
	public function __construct($p_strCountry)
	{
		$this->_country		= $p_strCountry;
		$this->numberDayOfWeek =7;
	}
	
	public function generateAuto($strCallback ="callback", $intIndex =-1)
	{
		if($strCallback)
		{
			$strCateName	= null;
			$objCategory	= new CCategory($this->_country);
			$arrData		= $objCategory->get();
			
			if(!$arrData){ 
				return;
			}
			
			$objSale		= new QSale($this->_country);
			$strMaxDate		= $objSale->get_max_date();
			
			$arrCategory	= isset($arrData['category']) ? $arrData['category'] : null;
			$arrParamT		= isset($arrData['T']) ? $arrData['T'] : null;
			
			#Cancellation Rate
			$intConfig				= 15;
			$arrDateConf			= $objSale->get_max_date(true);
			$this->_start_time		= CCommon::mkTimeDateFrom($arrDateConf['mdate']);
			//$this->_end_time		= CCommon::mkTimeDateFrom($arrDateConf['date'])-(60*60*24*$intConfig);
			$this->_end_time		= CCommon::mkTimeDateFrom($arrDateConf['mdate']) + (60*60*24*$intConfig);
			#Cancellation Rate
			
			if($intIndex>-1){
				$this->_run_category($arrCategory, $intIndex, $strMaxDate, $arrParamT, $this->_country);	
			}
			else
			{
				$intN		= count($arrCategory);
				for($i=0; $i<$intN; $i++){
					$this->_run_category($arrCategory, $i, $strMaxDate, $arrParamT, $this->_country);
				}
			}
			//Update forecasts day
		/*	if($this->_country !='vi')
			{ */
				$db					= Zend_Registry::get('db');
				$strQuery		= 'UPDATE '. $this->_country .'_forecasts f Inner join ' . $this->_country . '_suppliers s
				ON f.supplier_id = s.id SET ';
				
				if(CConfig::getCurrentDay()=="fri")
				{
					$strQuery		.=' f.forecasts_day = (s.order_frequency * 7) + 2 + s.leadtime;';
				}
				else
				{
					$strQuery		.=' f.forecasts_day = (s.order_frequency * 7) + s.leadtime;';	
				}
				
				$db->execute($strQuery);
				$db->closeConnection();
			/*} */
			
			echo $strCallback.'('.json_encode(array('result'=>$intIndex)).')';
		}
	}
	
	private function _run_category($p_arrCategory, $p_intIndex, $p_maxCofDate=null, $p_arrParamT=null)
	{
		ini_set('max_execution_time', 10000);
		//ini_set('display_errors' , 0);
		ini_set('session.gc_maxlifetime', '10000');
		ini_set('memory_limit', '-1');	
			
		$arrInfo		= isset($p_arrCategory[$p_intIndex]) ? $p_arrCategory[$p_intIndex] : null;
		if(!$arrInfo){ 
			 return;	
		}
		
		$strCateName	= $arrInfo['name'];
		
		#Consignment
		$strDateFrom	= $arrInfo['s_df'];
		$strDateTo		= $arrInfo['s_dt'];
		$intDayTarget	= intval($arrInfo['fd']);
		$intDayMinimum	= intval($arrInfo['bd']);
		
		#Outright
		$strDateFrom1	= $arrInfo['s_df1'];
		$strDateTo1		= $arrInfo['s_dt1'];
		$intDayTarget1	= intval($arrInfo['fd1']);
		$intDayMinimum1	= intval($arrInfo['bd1']);
		
		$w1	= isset($p_arrParamT['w1']) ? floatval($p_arrParamT['w1']) : 0 ;
		$w2	= isset($p_arrParamT['w2']) ? floatval($p_arrParamT['w2']) : 0 ;
		$w3	= isset($p_arrParamT['w3']) ? floatval($p_arrParamT['w3']) : 0 ;
		$w4	= isset($p_arrParamT['w4']) ? floatval($p_arrParamT['w4']) : 0 ;

		if( !$strCateName || !$strDateFrom || !$strDateTo ){
		 return;	
		}
		$objForecast	= new QBForecast($this->_country);
		$arrResult		= $objForecast->get_by_category($strCateName, null);
		
		if( $arrResult )
		{
			$objSale				= new QSale($this->_country);
			$arrSku					= array();
			$arrStockWMS			= array();
			$arrCancellationRate	= array();
	
			foreach($arrResult as $data)
			{
				if( !in_array($data['sku'], $arrSku) )
				{
					$arrSku[]		= $data['sku'];
					$arrTemp		= array();
				}
				if( !isset($arrCancellationRate[$data['sku']]) ){
					$arrCancellationRate[$data['sku']]	= array();
				}
				$arrCancellationRate[$data['sku']]	= $data['_cancellation_rate'];
			}
			
			$this->_arrCancellationRate	= $arrCancellationRate;
		
			$arrSkuData	= $objSale->get_item_analytic(implode('","', $arrSku), null, null);
			$intNSku	= count($arrSkuData);
			
			$intN		= count($arrResult);
			$intIndex_j	= 0;
			
			$intTime			= $p_maxCofDate ? CCommon::mkTimeDate($p_maxCofDate) : time();
			
			$intTimeLastMonth	= $intTime - 60*60*24*30;
			$intTimeLast21		= $intTime - 60*60*24*21;
			$intTimeLast14		= $intTime - 60*60*24*14;
			$intTimeLast7		= $intTime - 60*60*24*7;
			
			$strQueryUpdate		= null;
			$intIndexUpdate		= 0;
			$db					= Zend_Registry::get('db');
						
			for($i=0; $i<$intN; $i++)
			{				
				if(strtolower($arrResult[$i]['dummy2'])=='o')
				{
					$strDateFrom	= $arrInfo['s_df1'];
					$strDateTo		= $arrInfo['s_dt1'];
					
				/*	if($this->_country =='vi')
					{
						$intDayTarget	= intval($arrInfo['fd1']);
						$intDayMinimum	= intval($arrInfo['bd1']);
					}
					else
					{ */
						if(CConfig::getCurrentDay()=="fri")
						{
						$intDayTarget	=(isset($arrResult[$i]['order_frequency']) ? intval($arrResult[$i]['order_frequency']):0) * $this->numberDayOfWeek 
							+ 2 + (isset($arrResult[$i]['leadtime']) ? intval($arrResult[$i]['leadtime']):0);	
						}
						else
						{
						$intDayTarget	=(isset($arrResult[$i]['order_frequency']) ? intval($arrResult[$i]['order_frequency']):0) * $this->numberDayOfWeek
							+ (isset($arrResult[$i]['leadtime']) ? intval($arrResult[$i]['leadtime']):0);	
						}
						$intDayMinimum	= 0;
					/*} */
				}
				else
				{
					$strDateFrom	= $arrInfo['s_df'];
					$strDateTo		= $arrInfo['s_dt'];
					
					/*if($this->_country =='vi')
					{
						$intDayTarget	= intval($arrInfo['fd']);
						$intDayMinimum	= intval($arrInfo['bd']);
					}
					else
					{*/
						if(CConfig::getCurrentDay()=="fri")
						{
						$intDayTarget	= (isset($arrResult[$i]['order_frequency']) ? intval($arrResult[$i]['order_frequency']):0) * $this->numberDayOfWeek 
							+ 2 + (isset($arrResult[$i]['leadtime']) ? intval($arrResult[$i]['leadtime']):0);	
						}
						else
						{
						$intDayTarget	= (isset($arrResult[$i]['order_frequency']) ? intval($arrResult[$i]['order_frequency']):0) * $this->numberDayOfWeek
							+ (isset($arrResult[$i]['leadtime']) ? intval($arrResult[$i]['leadtime']):0);	
						}
						$intDayMinimum	= 0;
					/*}*/
				}
				
				$intTimeFrom		= CCommon::mkTimeDateFrom($strDateFrom);
				$intTimeTo			= CCommon::mkTimeDateTo($strDateTo);
				$intDayMinimum	= $intDayMinimum + intval($arrResult[$i]['dummy1']);
					
				$strSku			= $arrResult[$i]['sku'];
				$arrResult[$i]['msl']	= 0;
				$arrResult[$i]['tsl']	= 0;
				
				$arrSkuCount	= array();
				for($j=$intIndex_j; $j<$intNSku; $j++)
				{
					if($arrSkuData[$j]['sku']==$strSku)
					{
						for($k=$j; $k<$intNSku; $k++)
						{
							if($arrSkuData[$k]['sku']==$strSku){
								$arrSkuCount[]	= $arrSkuData[$k];
							}
							else
							{
								$intIndex_j = $k-1;
								break;
							}
						}
						break;
					}
				}
				
				$arrSkuAnalytic	= array();
				
				if($arrSkuCount)
				{
					$arrResult[$i]['t_4']			= 0;
					$arrResult[$i]['t_3']			= 0;
					$arrResult[$i]['t_2']			= 0;
					$arrResult[$i]['t_1']			= 0;
					$arrResult[$i]['total_sales']	= 0;
					
					foreach($arrSkuCount as $data)
					{
						$intTimeData	= CCommon::mkTimeDateFrom($data['date']);
						if($data["istatus"] !='cancelled')
						{
							if($intTimeData>=$intTimeLastMonth){
								$arrResult[$i]['t_4']	= intval($arrResult[$i]['t_4'])+1;
							}
							if($intTimeData>=$intTimeLast21){
								$arrResult[$i]['t_3']	= intval($arrResult[$i]['t_3'])+1;
							}
							if($intTimeData>=$intTimeLast14){
								$arrResult[$i]['t_2']	= intval($arrResult[$i]['t_2'])+1;
							}
							if($intTimeData>=$intTimeLast7){
								$arrResult[$i]['t_1']	= intval($arrResult[$i]['t_1'])+1;
							}
							
							$arrResult[$i]['total_sales'] = intval($arrResult[$i]['total_sales']) + 1;
						}
						if($intTimeData>=$intTimeFrom && $intTimeData<=$intTimeTo){
							$arrSkuAnalytic[]	= $data;
						}
					}
					$intT1		= $arrResult[$i]['t_1'];
					$intT2		= $arrResult[$i]['t_2'];
					$intT3		= $arrResult[$i]['t_3'];
					$arrResult[$i]['t_2']			= $arrResult[$i]['t_2'] - $intT1;
					$arrResult[$i]['t_3']			= $arrResult[$i]['t_3'] - $intT2;
					$arrResult[$i]['t_4']			= $arrResult[$i]['t_4'] - $intT3;
				}
				
				//$arrResult[$i]['total_sales']	= count($arrSkuCount);							
				
				#Target & Minimum
				$arrData		= self::cal_analytic($strSku, $strDateFrom, $strDateTo, $arrSkuAnalytic, $this, $this->_country);
				$mixAlpha		= isset($arrData['alpha']) ? round($arrData['alpha']) : 0;
				$mixRmse		= isset($arrData['rmse']) ? round($arrData['rmse']) : 0;
				$arrForecast	= isset($arrData['forecast_arr']) ? $arrData['forecast_arr'] : 0;
				
				$arrPForecast	= array();
				foreach($arrForecast as $value){
					$arrPForecast[]	= $value;
				}
				
				$arrData		= self::_getRegression($arrPForecast);
				$slope			= isset($arrData['slope']) ? $arrData['slope'] : 0;
				$yIntercept		= isset($arrData['yIntercept']) ? $arrData['yIntercept'] : 0;
				$lastX			= isset($arrData['lastX']) ? $arrData['lastX'] : 0;
				
				$arrData		= self::cal_TargetMinumum($slope, $yIntercept, $lastX, $intDayTarget, $intDayMinimum);	
				
				$arrResult[$i]['tsl']	= intval($arrData['target']);
				$arrResult[$i]['msl']	= intval($arrData['min']);
				#Target & Minimum
				
				$t_1Data = isset($arrResult[$i]['t_1'])? $arrResult[$i]['t_1'] : 0;
				$t_2Data = isset($arrResult[$i]['t_2'])? $arrResult[$i]['t_2'] : 0;
				$t_3Data = isset($arrResult[$i]['t_3'])? $arrResult[$i]['t_3'] : 0;
				$t_4Data = isset($arrResult[$i]['t_4'])? $arrResult[$i]['t_4'] : 0;
				
				$arrResult[$i]['moving_average']		
					= ($intDayTarget/7) * (($w1* $t_1Data)
							+ ($w2*($t_2Data - $t_1Data))
							+ ($w3*($t_3Data - $t_2Data))
							+ ($w4*($t_4Data - $t_3Data)));
				$arrResult[$i]['moving_average']		= round($arrResult[$i]['moving_average']);
				
				
				#Shipment Type
				$objSalesData	= new QSale($this->_country);
				$dataTmp  = $objSalesData->get_top_by_sku( $arrResult[$i]['sku']);
				
				$shipmentType ="Consignment";
				
				if(isset($dataTmp[0]['shipment_type']) && strtolower($dataTmp[0]['shipment_type']) =='o')
				{
					$shipmentType ="Outright";
				}
				
				#Update database
				$arrParams	= array
				(
					'moving_average'			=> $arrResult[$i]['moving_average'],
					'tsl'						=> $arrResult[$i]['tsl'],
					'msl'						=> $arrResult[$i]['msl'],
					'total_sales'				=> isset($arrResult[$i]['total_sales'])? $arrResult[$i]['total_sales'] : 0,
					'average_daily_sales'		=> round(floatval(isset($arrResult[$i]['total_sales']) ? $arrResult[$i]['total_sales'] : 0) / 30, 2),
					't_1'						=> isset($arrResult[$i]['t_1'])? $arrResult[$i]['t_1'] : 0,
					't_2'						=> isset($arrResult[$i]['t_2'])? $arrResult[$i]['t_2'] : 0,
					't_3'						=> isset($arrResult[$i]['t_3'])? $arrResult[$i]['t_3'] : 0,
					't_4'						=> isset($arrResult[$i]['t_4'])? $arrResult[$i]['t_4'] : 0,
					'_alpha'					=> $mixAlpha,
					'_rmse'						=> $mixRmse,
					'_slope'					=> $slope,
					'_intercept'				=> $yIntercept,
					'shipment_type'			   => $shipmentType
				);
			
				$arrUpdate		= array();
				foreach($arrParams as $key=>$value){
					$arrUpdate[]	= $key.'="'.$value.'"';
				}
				$strQuery		= 'UPDATE '.$objForecast->table().' SET '.implode(',', $arrUpdate).', 
				                             exponential_smoothening = tsl - asl - in_transit,
											 reorder= IF(asl + in_transit < msl, "Yes", "No") WHERE id='.intval($arrResult[$i]['id']);
				$strQueryUpdate	.= $strQuery.'; ';
				$intIndexUpdate++;
				
				if($intIndexUpdate>=20)
				{
					#echo htmlspecialchars($strQueryUpdate);
					$db->execute($strQueryUpdate, false);
					$intIndexUpdate	= 0;
					$strQueryUpdate	= null;
				}
				#$objForecast->update($arrParams, $arrResult[$i]['id']);
				#Update database
			}
			if($strQueryUpdate){
				#echo htmlspecialchars($strQueryUpdate);
				$db->execute($strQueryUpdate);
			}
		}
	}
	
	static function cal_analytic($p_strSku, $p_strDateFrom, $p_strDateTo, $p_arrItemAnalytic=null, $p_objCGenerate, $country)
	{
		set_time_limit(999);
		$strSku			= $p_strSku;
		$strDateFrom	= $p_strDateFrom;
		$strDateTo		= $p_strDateTo;
		
		$intTimeFrom	= CCommon::mkTimeDateFrom($strDateFrom);
		$intTimeTo		= CCommon::mkTimeDateTo($strDateTo);
		$intTime		= $intTimeFrom;
		$arrDate		= array();
		
		while($intTime<$intTimeTo)
		{
			$arrDate[]	= date('Y-m-d', $intTime);
			$intTime	= $intTime + (60*60*24);
		}
		
		
		$arrData		= $p_arrItemAnalytic;
		if(!$arrData)
		{
			$objSale	= new QSale($country);
			$arrData	= $objSale->get_item_analytic($strSku, $strDateFrom, $strDateTo);
		}
		$arrResult		= array();
		$arrForecast	= array();
		$alpha			= 0;
		$curError		= 0;
		if($arrData)
		{
			$arrTotal	= array();
			$arrCancel	= array();
			
			foreach($arrDate as $data)
			{
				$arrResult[$data]	= 0;
				$arrTotal[$data] 	= 0;
				$arrCancel[$data] 	= 0;
			}
			
			foreach($arrData as $data)
			{
				$_date	= $data['date'];
				if( in_array($data['istatus'], array('cancelled')) ){
					$arrCancel[$_date]++;
				}
				$arrTotal[$_date]++;
			}
			
			$average_rate	=	$p_objCGenerate->_arrCancellationRate[$p_strSku];
			
			foreach($arrDate as $date)
			{
				if($arrTotal[$date]<=0){
					continue;
				}
				$time	= CCommon::mkTimeDate($date);
				
				if($average_rate==0){
					$arrResult[$date]	=  $arrTotal[$date] - $arrCancel[$date];
				}
				else
				{
					if($time>=$p_objCGenerate->_start_time && $time<=$p_objCGenerate->_end_time){
						$arrResult[$date]	= $arrTotal[$date] - $arrCancel[$date];
					}
					else
					{
						$date_rate	= $arrTotal[$date]>0 ? $arrCancel[$date]/$arrTotal[$date] : 0;
						if($date_rate>=$average_rate){
							$arrResult[$date]	= $arrTotal[$date] - $arrCancel[$date];
						}
						else{
							$arrResult[$date]	= $arrTotal[$date] - $arrTotal[$date]*$date_rate;
						}
					}
				}
			}
			
			#Save Test
			$arrJson	= array
			(
				'sku'			=> $strSku,
				'average_rate'	=> $average_rate,
				'total'			=> $arrTotal,
				'cancel'		=> $arrCancel,
			);
			$strPath	= ROOT_SITE.'/data/_cancellation';
			@mkdir($strPath);
			file_put_contents($strPath.'/'.$strSku.'.data', json_encode($arrJson));
			#Save Test
		
			$step		= 0.1;
			$arrError	= array();
			for($i=$step; $i<=1; $i=$i+$step)
			{
				$mixError	= 0;
				self::_getForcast($i, $arrResult, $mixError);
				if($curError==0)
				{
					$curError	= $mixError;
					$alpha		= $i;
				}
				elseif($mixError<$curError)
				{
					$curError	= $mixError;
					$alpha		= $i;
				}
				$arrError['k_'.$i]	= $mixError;
			}
			
			if($alpha==0.1){
				$alpha=0.15;
			}
			elseif($alpha>=0.9){
				$alpha=0.85;
			}
			else
			{
				$start			= $alpha-$step;
				$end			= $alpha+$step;
				if($arrError['k_'.$start]<$arrError['k_'.$end]){
					$end		= $alpha;
				}
				elseif($arrError['k_'.$start]>$arrError['k_'.$start]){
					$start		= $alpha;
				}
				else{
					$end		= $alpha;
				}
				
				$step			= 0.0001;
				$alpha			= 0;
				$curError		= 0;
				for($i=$start; $i<=$end; $i=$i+$step)
				{
					$mixError	= 0;
					self::_getForcast($i, $arrResult, $mixError);
					if($curError==0)
					{
						$curError	= $mixError;
						$alpha		= $i;
					}
					elseif($mixError<$curError)
					{
						$curError	= $mixError;
						$alpha		= $i;
					}
				}
			}
			$arrForecast		= self::_getForcast($alpha, $arrResult, $curError);
		}
	
		return array
		(
			'date_arr'		=> $arrDate,
			'actual_arr'	=> $arrResult,
			'forecast_arr'	=> $arrForecast,
			'alpha'			=> $alpha,
			'rmse'			=> $curError
		);
	}
	
	static function _getForcast($p_alpha, $p_arrActual, &$p_mixError)
	{
		$arrResult	= array();
		if($p_arrActual)
		{
			$intIndex	= 0;
			$preActual	= 0;
			$preForecast= 0;
			foreach($p_arrActual as $value)
			{
				$mixForecast		= 0; 
				if($intIndex==0){
					$mixForecast	= $value;
				}
				else{
					$mixForecast	= ($p_alpha*$preForecast) + (1-$p_alpha)*$preActual;
				}
				$arrResult[]	= $mixForecast;
				$preActual		= $value;
				$preForecast	= $mixForecast;
				
				if(isset($p_mixError))
				{
					$mixBit		= $mixForecast-$value;
					$mixBit		= $mixBit>0 ? $mixBit : $mixBit*(-1);
					$p_mixError	= $p_mixError + pow($mixBit, 2);
				}
				
				$intIndex++;
			}
		}
		if(isset($p_mixError)){
			$p_mixError	= sqrt($p_mixError/count($p_arrActual));
		}
		return $arrResult;
	}
	
	static function cal_TargetMinumum($slope, $yIntercept, $lastX, $intDayTarget, $intDayMinimum)
	{
		$mixTarget		= 0;
		$mixMinimum		= 0;
		
		$intN			= $intDayTarget + $lastX;
		
		$intStartMinimum= intval($intN) - intval($intDayMinimum);	
		
		for($i=$lastX+1; $i<=$intN; $i++)
		{
			$mixValue	= ($slope*$i)+$yIntercept;	
			if($mixValue <= 0)
			{
				break;
			}

			$mixTarget	= $mixTarget + $mixValue;
			if($i > intval($intStartMinimum)){
				$mixMinimum	= $mixMinimum + $mixValue;
			}
		}

		$target = round($mixTarget,0);
		$min = round($mixMinimum ,0);	
		return array
		(
				'target'	=> $target,
				'min'		=> $min
		);
	}
	
	static function _getRegression($p_arrForecast)
	{
		require_once("LeastSquareRegression.php");
		bcscale(10);
		$leastSquareRegression = new LeastSquareRegression( 2 ); 
		$intN	= count($p_arrForecast);
		for($i=0; $i<$intN; $i++){
			$leastSquareRegression->addData($i, $p_arrForecast[$i] ); 
		}
		$coefficients = $leastSquareRegression->getCoefficients(); 
		
		$slope		= round( $coefficients[ 1 ], 5);
		$yIntercept	= round( $coefficients[ 0 ], 5);
		
	/*	$slope		= floatval($coefficients[ 1 ]);
		$yIntercept	= floatval($coefficients[ 0 ]); */
		
		$arrResult	= array();
		for($i=0; $i<$intN; $i++){
			$arrResult[]	= ($i*$slope) + $yIntercept; 
		}
		
		return array(
			'regress_arr'	=> $arrResult,
			'slope'			=> $slope,
			'yIntercept'	=> $yIntercept,
			'lastX'			=> count($arrResult)
		);
	}
}
?>